﻿using System;
using System.Collections.Generic;

namespace SortOf
{
    public class Set<T>
    {
               //a hash table is used to store the values
        private Dictionary<String, T> data;

        public Set()
        {
            data = new Dictionary<String, T>();
        }

        //returns true if a item could be added
        public bool add(T item)
        {
            //check if the key isn't in use aready
            if (data.ContainsKey(hash(item)) == false)
            {
                //if not, add the item to the table
                // since the hashTable needs a key/value pair the hash value is used as the key
                data.Add(hash(item), item);
                return true;
            }
            //if it is then false is returned
            return false;
        }       

        //a crude method for generating a hash code, causes a lot of collisions 
        //It's generated from the ASCII values of the string resulting from a tostring method
        private string hash(T item)
        {
            int hashValue = 0;
            char[] chars;
            string s = item.ToString();
            chars = s.ToCharArray();
            for (int i = 0; i <= chars.GetUpperBound(0); i++)
            {
                hashValue += (int)chars[i];
            }
            return hashValue.ToString();
        }
        //removes a Hashobject by hashcode
        public void remove(T item)
        {
            data.Remove(hash(item));
        }

        //returns the total number of Hashobject stored
        public int size()
        {
            return data.Count;
        }

        //the purpose of this method is solely to show the values in a console app, if you want to return the values of the set
        // I'd suggest making some kind of toArrayList method with a for-each loop.
        public void printSet()
        {
            Console.WriteLine("{0}{1,10}", "hash", "Value");
            

            foreach (KeyValuePair<String, T> Item in data)
            {
                Console.WriteLine("{0}{1,10}", Item.Key , Item.Value);
            }
            Console.WriteLine();
        }

        //union functie, combines 2 sets
        public Set<T> union(Set<T> aSet)
        {
            //create a temporary set that will contain the data of this set and the given second set
            Set<T> tempSet = new Set<T>();
            //adds the hashObjects from this set to the temporary set
            foreach (String hashObject in data.Keys)
            {
                tempSet.add(this.data[hashObject]);
            }
            //adds the hashObjects from the second set if it has a key that is not contained in this set.
            foreach (String hashObject in aSet.data.Keys)
            {
                if (!(this.data.ContainsKey(hashObject)))
                {
                    tempSet.add(aSet.data[hashObject]);
                }                    
            }
            return tempSet;
        }

        public Set<T> intersection(Set<T> aSet)
        {
            //create a temporary set that will contain data that is in both this and the second set
            Set<T> tempSet = new Set<T>();

            foreach (String hashObject in data.Keys)
            {
                //if the hashobject matches then add it to the temporary set
                if (aSet.data.ContainsKey(hashObject))
                {                    
                    tempSet.add(aSet.data[hashObject]);
                }
            }
            return tempSet;
        }

        public Set<T> difference(Set<T> aSet)
        {
            //this set will contain the hashobecjt that do appear in this set, but not in the second one
            Set<T> tempSet = new Set<T>();

            foreach (String hashObject in data.Keys)
            {
                //if if the hashobject isn't found ín the second set, it's added to the temporary set
                if (!(aSet.data.ContainsKey(hashObject)))
                {
                    tempSet.add(data[hashObject]);
                }
            }
            return tempSet;
        }

        //returns true if this set is a subset of the second set
        public bool isSubset(Set<T> aSet)
        {
            //returns false if this set isn't smaller, a subset is always smaller
            if (this.size() > aSet.size())
            {
                return false;
            }
            else
            {
                foreach (String key in this.data.Keys)
                {
                    //false is returned if a object in this set is not found in the second set.
                    if (!(aSet.data.ContainsKey(key)))
                    {
                        return false;
                    }
                }
                //if all objects from this set are found in the second then true is returned
                return true;
            }
        }

        public bool contains(T item)
        {
            //check if the set has a object with this hash
            if (this.data.ContainsKey(hash(item)))
            {
                //check if the object is really the same
                if (this.data[hash(item)].Equals(item))
                {
                    return true;
                }
            }
            return false;
        }
    }
}
